"""
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    created by lei.chang on '25/06/2023'
    comment: 全工程类
"""
import re
from datetime import datetime

from typing import Dict, Any, Type
from pydantic import PydanticValueError
from pydantic.validators import strict_str_validator, str_validator
from pydantic.errors import DateTimeError, DateError, TimeError, NotNoneError, IntegerError

from project.lib.constant import PHONE_REGEX, ID_NUMBER_REGEX
from project.core.base import UEnum, Element
from project.lib.common import Global


class PydanticErrors(UEnum):
    """ 参数校验所返回的报错信息
        code: 获取方式
          => 例如:
            from pydantic.errors import MissingError
            from pydantic.error_wrappers import get_exc_type

            print(get_exc_type(MissingError))
    """
    IntegerError = Element('type_error.integer', '表示字段不是数值错误', desc='[{0}] 类型应为数值型')
    ListError = Element('type_error.list', '表示字段不是列表错误', desc='[{0}] 类型应为列表型')
    DictError = Element('type_error.dict', '表示字段不是字典错误', desc='[{0}] 类型应为字典型')
    MissingError = Element('value_error.missing', '表示缺少必需字段的错误', desc='[{0}] 信息缺失')
    NoneIsNotAllowedError = Element('type_error.none.not_allowed', '表示字段不允许为 None 的错误。', desc='[{0}] 不允许为None')
    WrongConstantError = Element('value_error.const', '表示字段的值不是预期常量之一的错误', desc='[{0}] 不符合预期常量')
    StrError = Element('type_error.str', '表示字符串验证错误', desc='[{0}] 字符串格式验证失败')
    StrRegexError = Element('value_error.str.regex', '表示正则表达式格式错误', desc='[{0}] 字符串格式验证失败')
    AnyStrMinLengthError = Element('value_error.any_str.min_length', '表示字符串小于最小字符数', desc='[{0}] 字符串最少(>=) {1} 个字符')
    AnyStrMaxLengthError = Element('value_error.any_str.max_length', '表示字符串大于最大字符数', desc='[{0}] 字符串最多(<=) {1} 个字符')
    BoolError = Element('type_error.bool', '表示布尔值验证错误', desc='[{0}] 布尔值验证失败')
    NumberNotGtError = Element('value_error.number.not_gt', '表示数字值不大于指定值的错误', desc='[{0}] 数值要大于(>)最小值 {1}')
    NumberNotGeError = Element('value_error.number.not_ge', '表示数字值不大于或等于指定值的错误', desc='[{0}] 数值要大于等于(>=)最小值 {1}')
    NumberNotLtError = Element('value_error.number.not_lt', '表示数字值不小于指定值的错误', desc='[{0}] 数值要小于(<)最大值 {1}')
    NumberNotLeError = Element('value_error.number.not_le', '表示数字值不小于或等于指定值的错误', desc='[{0}] 数值要小于等于(<=)最大值 {1}')
    NumberNotMultipleError = Element('value_error.number.not_multiple', '表示数字值不是指定倍数的错误', desc='[{0}] 需要是 {1} 的倍数')
    DateTimeError = Element('value_error.datetime', '表示日期时间格式错误', desc='[{0}] 日期时间格式验证失败')
    DateError = Element('value_error.date', '表示日期格式错误', desc='[{0}] 日期格式验证失败')
    TimeError = Element('value_error.time', '表示时间格式错误', desc='[{0}] 时间格式验证失败')
    DurationError = Element('value_error.duration', '表示时间段格式错误', desc='[{0}] 时间格式验证失败')
    UUIDError = Element('type_error.uuid', '表示UUID格式错误', desc='[{0}] uuid格式验证失败')
    UrlError = Element('value_error.url', '表示URL格式错误', desc='[{0}] url格式验证失败')
    EmailError = Element('value_error.email', '表示电子邮件格式错误', desc='[{0}] 邮件格式验证失败')
    PhoneError = Element('value_error.phone', '表示手机号格式错误', desc='[{0}] 手机号格式验证失败')
    IdNumberError = Element('value_error.id_number', '表示身份证格式错误', desc='[{0}] 身份证格式验证失败')
    EnumMemberError = Element('type_error.enum', '表示值不在枚举类型范围呢', desc='[{0}] 值不在枚举成员列表内')


class PhoneError(PydanticValueError):
    code = 'phone'
    msg_template = 'value is not a phone number'


class IdNumberError(PydanticValueError):
    code = 'id_number'
    msg_template = 'value is not a id number'


class ValidatorStr(str):
    """
    """
    code: str = ''

    @classmethod
    def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None:
        field_schema.update(type='string', format=cls.code)

    @classmethod
    def __get_validators__(cls):
        yield cls.validate

    @classmethod
    def validate(cls, value):
        pass

    @classmethod
    def check(cls, value) -> bool:
        """
        :param value:
        :return:
        """
        try:
            cls.validate(value)
            return True
        except:
            return False


class PhoneStr(ValidatorStr):
    code = 'phone'

    @classmethod
    def validate(cls, value):
        """
        :param value:
        :return:
        """
        value = strict_str_validator(value)
        if not value:
            return value

        regex = re.compile(PHONE_REGEX)
        if not regex.match(value):
            raise PhoneError
        return value


class IdNumberStr(ValidatorStr):
    code = 'id_number'

    @classmethod
    def validate(cls, value):
        """
        :param value:
        :return:
        """
        value = strict_str_validator(value)
        if not value:
            return value

        # 验证身份证号码格式是否正确
        regex = re.compile(ID_NUMBER_REGEX)
        if not regex.match(value):
            raise IdNumberError

        # 验证身份证号码中的地区码是否正确
        province_code = value[:2]
        if province_code not in ('11', '12', '13', '14', '15', '21', '22', '23', '31', '32',
                                 '33', '34', '35', '36', '37', '41', '42', '43', '44', '45',
                                 '46', '50', '51', '52', '53', '54', '61', '62', '63', '64',
                                 '65', '71', '81', '82', '91'):
            raise IdNumberError

        # 验证身份证号码中的出生日期是否正确
        birthday = value[6:14]
        try:
            datetime.strptime(birthday, '%Y%m%d')
        except ValueError:
            raise IdNumberError

        # 验证身份证号码中的校验码是否正确
        factors = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
        check_sum = sum(int(value[i]) * factors[i] for i in range(17))
        check_code = '10X98765432'[check_sum % 11]
        if check_code != value[-1].upper():
            raise IdNumberError
        return value


class DateTimeStr(ValidatorStr):
    code = 'datetime'
    none_enable = False
    blank_enable = False
    ret_type = 'datetime'  # 有 datetime/str 两种类型

    @classmethod
    def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None:
        description = '%Y-%m-%d %H:%M:%S | %Y%m%d%H%M%S | %Y/%m/%d %H:%M:%S | 时间戳'
        if cls.none_enable:
            description += ' | None'
        if cls.blank_enable:
            description += ' | ""'
        field_schema.update(type='string | int', format=cls.code, description=description)

    @classmethod
    def validate(cls, value):
        """
        :param value:
        :return:
        """
        if value is None:
            if not cls.none_enable:
                raise NotNoneError
            else:
                return value

        value = str_validator(value).strip()

        if not value:
            if not cls.blank_enable:
                raise DateTimeError
            else:
                return value

        try:
            if not value.isdigit():
                # %Y-%m-%d %H:%M:%S
                if len(value) == 19:
                    value = f'{value[0:4]}-{value[5:7]}-{value[8:10]} {value[11:13]}:{value[14:16]}:{value[17:19]}'
                # %Y%m%d%H%M%S
                elif len(value) == 14:
                    value = f'{value[0:4]}-{value[4:6]}-{value[6:8]} {value[8:10]}:{value[10:12]}:{value[12:14]}'
                else:
                    raise DateTimeError

                value = datetime.strptime(value, '%Y-%m-%d %H:%M:%S')

            # 时间长整型
            else:
                value = datetime.fromtimestamp(int(value[0:10]))
        except:
            raise DateTimeError

        return value


class DateStr(ValidatorStr):
    code = 'date'
    none_enable = False
    blank_enable = False
    ret_type = 'date'  # 有 date/str 两种类型

    @classmethod
    def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None:
        description = '%Y-%m-%d | %Y%m%d | %Y/%m/%d'
        if cls.none_enable:
            description += ' | None'
        if cls.blank_enable:
            description += ' | ""'
        field_schema.update(type='string', format=cls.code, description='description')

    @classmethod
    def validate(cls, value):
        """
        :param value:
        :return:
        """
        if value is None:
            if not cls.none_enable:
                raise NotNoneError
            else:
                return value

        value = str_validator(value).strip()

        if not value:
            if not cls.blank_enable:
                raise DateError
            else:
                return value

        try:
            # %Y-%m-%d
            if len(value) == 10:
                formatted_value = f'{value[0:4]}-{value[5:7]}-{value[8:10]}'
            # %Y%m%d
            elif len(value) == 8:
                formatted_value = f'{value[0:4]}-{value[4:6]}-{value[6:8]}'
            else:
                raise DateError

            value = datetime.strptime(formatted_value, '%Y-%m-%d').date()

        except:
            raise DateError

        return value


class TimeStr(ValidatorStr):
    code = 'time'
    none_enable = False
    blank_enable = False
    ret_type = 'time'  # 有 time/str 两种类型

    @classmethod
    def validate(cls, value):
        """
        :param value:
        :return:
        """
        if value is None:
            if not cls.none_enable:
                raise NotNoneError
            else:
                return value

        value = str_validator(value).strip()

        if not value:
            if not cls.blank_enable:
                raise TimeError
            else:
                return value

        try:
            # %H-%M-%S
            if len(value) == 8:
                formatted_value = f'{value[0:2]}:{value[3:5]}:{value[6:8]}'
            # %H%M%S
            elif len(value) == 6:
                formatted_value = f'{value[0:2]}:{value[2:4]}:{value[4:6]}'
            else:
                raise TimeError

            value = datetime.strptime(formatted_value, '%H:%M:%S').time()

        except:
            raise TimeError

        return value


class IntQuery(int):
    """
    """
    code: str = 'integer'

    @classmethod
    def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None:
        field_schema.update(type='integer', format=cls.code)

    @classmethod
    def __get_validators__(cls):
        yield cls.validate

    @classmethod
    def validate(cls, value):
        if not value:
            return 0
        if not value.isdigit():
            raise IntegerError

        return int(value)


def generate_validator(class_: Type[ValidatorStr],
                       none_enable: bool = False,
                       blank_enable: bool = False):
    """ 生成新的校验类
    :param class_:
    :param none_enable:
    :param blank_enable:
    :param ret_type:
    :return:
    """
    attrs = {
        'none_enable': none_enable,
        'blank_enable': blank_enable
    }

    return type(f'ValidatorStr{Global.count()}', (class_,), attrs)


if __name__ == '__main__':

    pass
